/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief SQLResult implementation.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * $Id: SQLResult.cpp,v 1.6 2009-08-11 08:03:43 amaula Exp $
 */
#include "owndebug.h"
#include "SQLResult.hpp"
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
using namespace MaCI::SQL;
//*****************************************************************************
std::string CSQLResult::iResultVersion = MACI_SQL_SQLRESULT_VERSION_STRING;
//*****************************************************************************
//*****************************************************************************
    

CSQLResult::CSQLResult(void)
  : iXMLData(),
    iResultType(KUnknown),
    iTable(),
    iLastError(),
    iAffectedRows(-1)
{
}
//*****************************************************************************

CSQLResult::CSQLResult(const std::string &aXMLData)
  : iXMLData(aXMLData),
    iResultType(KUnknown),
    iTable(),
    iLastError(),
    iAffectedRows(-1)
{
  DecodeSQLResultXML(aXMLData);
}
//*****************************************************************************

CSQLResult::~CSQLResult(void)
{
}
//*****************************************************************************

void CSQLResult::Reset(void)
{
  iXMLData.clear();
  iTable.Reset();
}
//*****************************************************************************

bool CSQLResult::EncodeSQLResultXML(void)
{
  return EncodeSQLResultXML(iXMLData);
}
//*****************************************************************************

bool CSQLResult::EncodeSQLResultXML(std::string &aXMLData) const
{
  bool result = true;

  // Clear internal result.
  aXMLData.clear();
  
  // XML header
  aXMLData += "<?xml version=\"1.0\"?>\n";

  // sqlresult root tag
  aXMLData += "<sqlresult version=\"" + iResultVersion + "\">\n";

  // Do it!
  switch(iResultType) {
  case KTable:
    result = EncodeTagTable(aXMLData, iTable);
    break;

  case KError:
    result = EncodeTagError(aXMLData, iLastError);
    break;

  case KAffectedRows: 
    result = EncodeTagAffected(aXMLData, iAffectedRows);
    break;

  default:
    dPrint(ODERROR,"Container type is Unknown! Will not encode!");
    result = false;
    break; 
  }
  
  // sqlresult end tag
  aXMLData += "</sqlresult>\n";

  return result;
}
//*****************************************************************************

bool CSQLResult::DecodeSQLResultXML(const std::string &aXMLData)
{
  xmlDocPtr docp;
  xmlNodePtr nodep;
  bool result = false;

  docp = xmlRecoverDoc((xmlChar *) aXMLData.c_str());
  if (docp == NULL) {
    dPrint(ODWARN,"Failed to decode XML doc. Fatal error. Decoding aborted.");
    return false;
  }
  
  // Get root element
  nodep = xmlDocGetRootElement(docp);
  assert(nodep != NULL);

  // Check that we have a Machine
  if( xmlStrcmp(nodep->name, (xmlChar *) "sqlresult")) {
    dPrint(ODWARN, "This is not a SQLResult XML document (Invalid root node: '%s')",
           nodep->name);
    
  } else {
    
    // Check version.
    xmlChar *sqlresult_version = xmlGetProp(nodep, (xmlChar *) "version");
    if (sqlresult_version) {
      dPrint(ODDEBUG(0), "SQLResult version is '%s'", sqlresult_version);
      
      // Check whether file version matches current (or list of supported versions : UNIMPLEMENTED - FIXME)
      if (std::string((const char*)sqlresult_version) == iResultVersion) {
        dPrint(ODDEBUG(0),"SQLResult matches supported version (%s), so far so good.", 
               iResultVersion.c_str());
      } else {
        dPrint(ODWARN,"Warning: Given XMLResult is version '%s', while current SQLResult decoder is version '%s'",
               sqlresult_version, iResultVersion.c_str());
      }


      xmlFree(sqlresult_version);
      sqlresult_version = NULL;
    } else {
      dPrint(ODWARN,"Unknown SQLResult version - Attempting to decode anyway (But expect problems!)");
      
    }

    // Enter children level of root.
    nodep = nodep->children;
    
    // parse 
    result = true;
    while(nodep && result) {
      if (nodep->type == XML_ELEMENT_NODE) {
        if ( !xmlStrcmp(nodep->name, (xmlChar *) "error") ) {
          result = DecodeTagError(docp, nodep);
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "table") ) {
          result = DecodeTagTable(docp, nodep);
          
        } else if ( !xmlStrcmp(nodep->name, (xmlChar *) "affected") ) {
          result = DecodeTagAffected(docp, nodep);

        } else {
          dPrint(ODERROR,"Unknown tag '%s' in level 0", 
                 nodep->name);

        }
      }
      
      // Traverse to next node in level.
      nodep = nodep->next;
    }
  }
  
  // Free doc.
  if (docp) xmlFreeDoc(docp);


  // Last but not least, if the decoding was succesfull, store the
  // given input XML-data.
  if (result) iXMLData = aXMLData;

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//**** private ****************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

bool CSQLResult::EncodeTagField(std::string &aXML, const CSQLTable &t, 
                                   const unsigned int aRow, 
                                   const unsigned int aColumn) const
{
  bool result = true;

  const CSQLField &f = t.GetField(aRow, aColumn);
  if (f.IsNULL()) {
    // Is NULL value?
    aXML += "   <f flag=\"NULL\"/>\n";

  } else {
    // Got value (can be empty string)
    aXML += "   <f>" + f.GetValueString() + "</f>\n";

  }

  return result;
}
//*****************************************************************************

bool CSQLResult::EncodeTagRow(std::string &aXML, const CSQLTable &t, 
                                 const unsigned int aRow) const
{
  bool result = true;

  // Create + cat table line
  aXML += "  <r>\n";
  
  // Process column contents
  const unsigned int cols = t.GetColumnCount();
  for(unsigned int i = 0; i < cols; ++i) {
    result = EncodeTagField(aXML, t, aRow, i);    
  }
  
  // Terminate colum line
  aXML += "  </r>\n";

  return result;
}
//*****************************************************************************

bool CSQLResult::EncodeTagTable(std::string &aXML, const CSQLTable &t) const
{
  bool result = true;

  // Create + cat table line
  char formatb[512];
  sprintf(formatb, " <table cols=\"%u\" rows=\"%u\">\n",
          t.GetColumnCount(),
          t.GetRowCount());
  aXML += formatb;
  
  // Process table contents, row by row.
  const unsigned int rows = t.GetRowCount();
  for(unsigned int i = 0; i < rows; ++i) {
    result = EncodeTagRow(aXML, t, i);    
  }

  
  // Terminate table line
  aXML += " </table>\n";

  return result;
}
//*****************************************************************************

bool CSQLResult::EncodeTagError(std::string &aXML, const std::string &aError) const
{
  bool result = true;

  // Just encode the error tag.
  aXML += " <error>" + aError + "</error>\n";
  
  return result;
}
//*****************************************************************************

bool CSQLResult::EncodeTagAffected(std::string &aXML, const long long int &aAffected) const
{
  bool result = true;

  // Just encode the error tag.
  char value[64];
  sprintf(value,"%lld", aAffected);
  aXML += " <affected>" + std::string(value) + "</affected>\n";
  
  return result;
}
//*****************************************************************************

bool CSQLResult::DecodeTagAffected(xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = true;
  
  // Extract content from Error tag.
  xmlChar *content = xmlNodeListGetString(docp, nodep->xmlChildrenNode, 0);
  if (content) {
    const long long int aff = atoll((const char *)content);
    iAffectedRows = aff;
    iResultType = KAffectedRows;

  } else {
    dPrint(ODERROR,"Got 'affected' tag but with no content! Invalid result!");
    result = false;

  }

  return result;
}
//*****************************************************************************

bool CSQLResult::DecodeTagError(xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = true;

  // Extract content from Error tag.
  xmlChar *content = xmlNodeListGetString(docp, nodep->xmlChildrenNode, 0);
  if (content) {
    iLastError = (const char *)content;
    xmlFree(content);
    iResultType = KError;

  } else {
    dPrint(ODERROR,"Got 'error' tag but with no content! Invalid result!");
    result = false;

  }

  return result;
}
//*****************************************************************************

bool CSQLResult::DecodeTagTable(xmlDocPtr docp, xmlNodePtr nodep)
{
  bool result = true;

  // New table to start filling.
  CSQLTable ntable;

  xmlChar *colcountprop = xmlGetProp(nodep, (xmlChar *) "cols");
  xmlChar *rowcountprop = xmlGetProp(nodep, (xmlChar *) "rows");
  
  if (colcountprop &&
      rowcountprop) {
    
    // Assign table info.
    ntable.SetDimensions( atoi((const char *)rowcountprop), atoi((const char *)colcountprop) );
    iResultType = KTable;
    
  } else {
    dPrint(ODERROR,"Invalid 'table' tag (not all required properties present)");
    result = false; // Set to false, while() loop lower will skip and exit.

  }

  // Cleanup (Always do this to clean out properties which were found)
  if (colcountprop) xmlFree(colcountprop);
  if (rowcountprop) xmlFree(rowcountprop);
  
  // Take children
  xmlNodePtr subnodep = nodep->children;
  
  // Iterate children.
  while(subnodep && result) {
    if (subnodep->type == XML_ELEMENT_NODE) {
      if ( !xmlStrcmp(subnodep->name, (xmlChar *) "r") ) {
        // Action for table.
        result = DecodeTagRow(docp, subnodep, ntable);
        
      } else {
        dPrint(ODERROR,"Unknown tag '%s' in level 1", 
               subnodep->name);
        
      }
      
    }
    
    // Traverse to next node in level.
    subnodep = subnodep->next;
  }

  // If table was OK, add it!
  if (result) {
    iTable = ntable;
  }


  return result;
}
//*****************************************************************************

bool CSQLResult::DecodeTagRow(xmlDocPtr docp, xmlNodePtr nodep, CSQLTable &table)
{
  bool result = true;
  
  // Take children
  xmlNodePtr subnodep = nodep->children;

  // Create new Row result.
  TSQLRow newrow;
  
  // Iterate children.
  while(subnodep && result) {
    if (subnodep->type == XML_ELEMENT_NODE) {
      if ( !xmlStrcmp(subnodep->name, (xmlChar *) "f") ) {
        // Action for table.
        result = DecodeTagField(docp, subnodep, newrow);
                
      } else {
        dPrint(ODERROR,"Unknown tag '%s' in level 2", 
               subnodep->name);
        
      }
      
    }
    
    // Traverse to next node in level.
    subnodep = subnodep->next;
  }

  // If ok, add row.
  if (result) {
    result = table.AddRow(newrow);
  }


  return result;
}
//*****************************************************************************

bool CSQLResult::DecodeTagField(xmlDocPtr docp, xmlNodePtr nodep, TSQLRow &row)
{ 
  bool result = true;
  CSQLField nf;
  
  // Check for existence of field flag property.
  xmlChar *flagprop = xmlGetProp(nodep, (xmlChar *) "flag");
  if (flagprop) {
    const std::string flagstr((const char *)flagprop);
    if (flagstr == "NULL") {
      dPrint(ODTEST,"Got NULL field value (This print exists, as this is experimental feature :)");
      // Field is NULL.
      nf.SetNULL(true);

    } else {
      dPrint(ODWARN,"Invalid flag '%s' - Ignore", flagstr.c_str());

    }
    xmlFree(flagprop);
  }

  // Extract content
  xmlChar *content = xmlNodeListGetString(docp, nodep->xmlChildrenNode, 0);
  if (content) {
    
    // Do little check whether the field was set to NULL by a flag.
    if (nf.IsNULL()) {
      dPrint(ODERROR,"Encoding error! Field cannot be NULL and still contain data!");
      result = false;

    } else {
      
      // Construct new field with information.
      nf.SetValueString((const char *)content);
    }
    
    xmlFree(content);
  } 
  
  // Store field in case result is OK. Note that the element may be
  // inserted even it is NOT NULL and content was NULL - in this case
  // the string just was empty.
  if (result) row.push_back(nf);

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
